﻿using System;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;


namespace Loef
{
	public static class QueryExtensions
	{
		/// <summary>
		/// (This just casts IQueryable to ObjectQuery and calls that extension method).
		/// We still need to be able to do eager loading when all we have are strings that indicate
		/// the association paths to be loaded (like when they're being passed from a REST service.  The paths can only come in as strings).
		/// This method will take a string that represents multiple association paths to eagerly load, separated by commas.
		/// Ex: If you're loading a Parent object, you could pass in "Children.Children,Siblings" to load:
		/// Parent.Children
		/// For each child, Child.Children
		/// Parent.Siblings
		/// </summary>
		public static IQueryable<T> DoIncludes<T>(this IQueryable<T> query, string commaSeparatedIncludes)
		{
			return ((ObjectQuery<T>) query).DoIncludes(commaSeparatedIncludes);
		}

		/// <summary>
		/// We still need to be able to do eager loading when all we have are strings that indicate
		/// the association paths to be loaded (like when they're being passed from a REST service.  The paths can only come in as strings).
		/// This method will take a string that represents multiple association paths to eagerly load, separated by commas.
		/// Ex: If you're loading a Parent object, you could pass in "Children.Children,Siblings" to load:
		/// Parent.Children
		/// For each child, Child.Children
		/// Parent.Siblings
		/// </summary>
		public static ObjectQuery<T> DoIncludes<T>(this ObjectQuery<T> query, string commaSeparatedIncludes)
		{
			if (commaSeparatedIncludes != null)
				foreach (string include in commaSeparatedIncludes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries))
					query = query.Include(include);
			return query;
		}


		/// <summary>
		/// While IQuerable has a lot of options for OrderBy() (like passing in the name of the column as a string), OrderByDescending() does not have
		/// all those options.  This eases that pain.
		/// </summary>
		private static IOrderedQueryable<T> OrderingHelper<T>(IQueryable<T> source, string propertyName, bool descending, bool anotherLevel)
		{
			ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
			MemberExpression property = Expression.PropertyOrField(param, propertyName);
			LambdaExpression sort = Expression.Lambda(property, param);

			MethodCallExpression call = Expression.Call(
				typeof(Queryable),
				(!anotherLevel ? "OrderBy" : "ThenBy") + (descending ? "Descending" : string.Empty),
				new[] { typeof(T), property.Type },
				source.Expression,
				Expression.Quote(sort));

			return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(call);
		}

		/// <summary>
		/// While IQuerable has a lot of options for OrderBy() (like passing in the name of the column as a string), OrderByDescending() does not have
		/// all those options.  This eases that pain.
		/// </summary>
		public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source, string propertyName, bool descending)
		{
			return OrderingHelper(source, propertyName, descending, false);
		}

		/// <summary>
		/// While IQuerable has a lot of options for OrderBy() (like passing in the name of the column as a string), OrderByDescending() does not have
		/// all those options.  This eases that pain.
		/// </summary>
		public static IOrderedQueryable<T> ThenOrder<T>(this IOrderedQueryable<T> source, string propertyName, bool descending)
		{
			return OrderingHelper(source, propertyName, descending, true);
		}
	}

}
